package com.onpositive.knowledge.model.values;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.onpositive.data.IPropertyConstants;
import com.onpositive.knowledge.model.value.audits.AbstractAuditResult;
import com.onpositive.knowledge.model.value.audits.GeneralAuditResult;
import com.onpositive.knowledge.model.value.audits.RangeAuditResult;
import com.onpositive.semantic.model.api.property.IProperty;
import com.onpositive.units.ParsedDate;
import com.onpositive.units.ParsedDimension;
import com.onpositive.units.ParsedScalar;
import com.onpositive.units.ParsedValue;
import com.onpositive.units.RangeValue;
import com.onpositive.units.ValueCluster;

public class StatisticValueTransformer extends AuditAssistedTransformer {
	
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 7452635155899060011L;
	private static final String comment = "Out of range value" ;
	public static final String ID = "statistic-value-transformer";
	private static final String META_KEY_INDIVIDUAL_VALUES_ARRAY = "individual-values-array";
	private static final String META_KEY_AUDIT_RESULT = "audit-result";

	public StatisticValueTransformer( IHasAuditResult source ) {
		super(source);
	}

	@Override
	public DescribedValue[] adjust(Object object, IProperty prop, DescribedValue... original)
	{
		Boolean isService = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_IS_SERVICE_PROPERTY, Boolean.class, null) ;
		if( isService != null && isService )
			return original ;
		
		Boolean isDepricated = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_DEPRICATED_LABEL, Boolean.class, null) ;
		if( isDepricated != null && isDepricated )
			return original ;
		
		List<RangeAuditResult> auditResultList = null ;
		AbstractAuditResult aResult = this.getAuditResultSource().getAuditResult() ;
		
		if( aResult instanceof RangeAuditResult ){
			auditResultList = new ArrayList<RangeAuditResult>() ;
			auditResultList.add( (RangeAuditResult) aResult ) ;
		}		
		else if( aResult instanceof GeneralAuditResult )
		{			
			GeneralAuditResult gResult = (GeneralAuditResult) aResult ;
			auditResultList = gResult.getResults( prop.getId(), null, RangeAuditResult.class ) ;
		}
		if( auditResultList == null || auditResultList.size() == 0 )
			return original ;
		
		String valueType = prop.getMeta().getSingleValue( IPropertyConstants.CHAR_PARSER_ID_LABEL, String.class, null ) ;
		
		ArrayList<? extends ParsedValue> individualValuesList = createIndividualArray(valueType) ; 
		if( individualValuesList == null )
			return original ;		
		
		OwnTraverser<?> collector = traverserMap_col.get( valueType ) ;
		if( collector == null )
			return original;
		
		OwnModifyingTraverser<?> modifyer = traverserMap_mod.get( valueType ) ;
		if( modifyer == null )
			return original;
		
		HashMap<String,Object> metaInfo = new HashMap<String, Object>() ;
		metaInfo.put( META_KEY_AUDIT_RESULT, auditResultList.iterator().next() ) ;
		metaInfo.put( META_KEY_INDIVIDUAL_VALUES_ARRAY , individualValuesList ) ;
		
		collector.iterateValues(prop, original, false, metaInfo, 0) ;		
		
		DescribedValue[] processed = (DescribedValue[]) modifyer.iterateValues(prop, original, false, metaInfo, 0, false) ;
		return processed != null ? processed : original ;		
	}
	
	private static ArrayList<? extends ParsedValue> createIndividualArray( String valueType)
	{
		if( valueType.equals(ParsedDimension.ID) )
			return new ArrayList<ParsedDimension>() ;
			
		if( valueType.equals(ParsedScalar.ID) )
			return new ArrayList<ParsedScalar>() ;
		
		if( valueType.equals( ParsedDate.ID) )
			return new ArrayList<ParsedDate>() ;
		
		return null ;
	}

	private HashMap<String,OwnModifyingTraverser<?>> traverserMap_mod = new HashMap<String, OwnModifyingTraverser<?>>() ;
	{
		traverserMap_mod.put( ParsedDimension.ID, new OwnModifyingTraverser<ParsedDimension>( ParsedDimension.class ) ) ;
		traverserMap_mod.put( ParsedScalar.ID, new OwnModifyingTraverser<ParsedScalar>( ParsedScalar.class ) ) ;
		traverserMap_mod.put( ParsedDate.ID, new OwnModifyingTraverser<ParsedDate>( ParsedDate.class ) ) ;
	}
	
	private HashMap<String,OwnTraverser<?>> traverserMap_col = new HashMap<String, OwnTraverser<?>>() ;
	{
		traverserMap_col.put( ParsedDimension.ID, new OwnTraverser<ParsedDimension>( ParsedDimension.class ) ) ;
		traverserMap_col.put( ParsedScalar.ID, new OwnTraverser<ParsedScalar>( ParsedScalar.class ) ) ;
		traverserMap_col.put( ParsedDate.ID, new OwnTraverser<ParsedDate>( ParsedDate.class ) ) ;
	}

	private static class OwnModifyingTraverser<T extends RangeValue<?>> extends ModifyingValueTraverser
	{
		private final Class<T> targetClass ;
		
		private OwnModifyingTraverser( Class<T> targetClass ){
			super( new Class<?>[]{ targetClass, ValueCluster.class, DescribedValue.class },
					new Class<?>[]{ targetClass, ValueCluster.class, DescribedValue.class } ) ;			
			this.targetClass = targetClass ;
		}
		@SuppressWarnings("unchecked")
		@Override
		protected Object process( IProperty prop, Object val, Map<String, Object> metaInfo, int index,boolean cameFromCollection)
		{
			if( val instanceof DescribedValue){
				
				DescribedValue dv = (DescribedValue) val ;
				Object newVal = iterateValues(prop, dv.value() , false, metaInfo, 0,false) ;
				if( newVal == null )
					return null ;
				
				return new DescribedValue( newVal, comment ) ;
			}
			else{				
				T pVal ;
				if( targetClass.isInstance(val) ){
					pVal = targetClass.cast( val ) ;
				}
				else if( val instanceof ValueCluster ){
					ValueCluster<?> vc = (ValueCluster<?>) val ;
					Class<?> clusterClass = vc.getTargetClass() ;
					if( clusterClass != targetClass )
						return null;
					
					pVal = targetClass.cast( vc.getTargetValue() ) ;
				}
				else
					return null;
				Double[] values = pVal.getConvertedValue( Double.class ) ;
				
				RangeAuditResult  auditResult = (RangeAuditResult)  metaInfo.get(META_KEY_AUDIT_RESULT) ;
				double lb = auditResult.getLowerBound() ;
				double ub = auditResult.getUpperBound() ;
				
				metaInfo.remove("is-suspicious") ;
				ArrayList<T> individualValuesArray = (ArrayList<T>) metaInfo.get(META_KEY_INDIVIDUAL_VALUES_ARRAY) ;
				
				for( Double dVal : values ){
					boolean suspicious = dVal < lb || dVal > ub;
					
					byte validity = Byte.MAX_VALUE ;
					if( suspicious )
						validity = Byte.MIN_VALUE ;
//					else{
//						double eps = 0.01*(ub-lb) ;
//						if( Math.abs(dVal-ub) < eps || Math.abs(dVal-lb) < eps )
//						{
//							suspicious = true ;
//							validity = Byte.MAX_VALUE >> 1 ;
//						}
//					}
					
					if( suspicious ){
						T suspiciousValue = (T) pVal.clone( values, ID ) ;
						if (suspiciousValue!=null){
						suspiciousValue.setValidity( validity ) ;
						return suspiciousValue ;
						}
					}					
				}				
				return null ;
			}
		}
		
		@Override
		protected Class<?>[] createTargetClasses() {
			return null;
		}
		
		@Override
		protected Class<?>[] createNecessaryClasses() {
			return null;
		}
	};
	
	private static class OwnTraverser<T extends RangeValue<?>> extends ValueTraverser
	{
		
		private final Class<T> targetClass ;
		
		private OwnTraverser( Class<T> targetClass ){
			super( new Class<?>[]{ targetClass, ValueCluster.class, DescribedValue.class },
					new Class<?>[]{ targetClass, ValueCluster.class, DescribedValue.class } ) ;			
			this.targetClass = targetClass ;
		}
		
		@Override
		@SuppressWarnings("unchecked")
		protected void process(IProperty prop, Object val, Map<String, Object> metaInfo, int index) {			
			
			ArrayList<T> individualValuesArray = (ArrayList<T>) metaInfo.get(META_KEY_INDIVIDUAL_VALUES_ARRAY) ;
			
			T pVal = null ;
			if( targetClass.isInstance(val) ){
				pVal = targetClass.cast( val ) ;
			}
			else if( val instanceof ValueCluster ){
				ValueCluster<?> vc = (ValueCluster<?>) val ;
				Class<?> clusterClass = vc.getTargetClass() ;
				if( clusterClass != targetClass )
					return;
				
				pVal = targetClass.cast( vc.getTargetValue() ) ;
			}
			
			if( pVal == null )
				return ;			
			
			individualValuesArray.add(pVal) ;			
		}		
		
		@Override
		protected Class<?>[] createTargetClasses() {
			return null;
		}
		
		@Override
		protected Class<?>[] createNecessaryClasses() {
			return null;
		}
	
	};

	@Override
	public String getId() {
		return ID;
	}

	

}
